// 仿mobx的观察者模式实现

import { Update_mode } from "./Enums";

class ObserverMgr {
    private _f_t_key: Map<Function, Map<Object, Set<string>>> = new Map();
    private _t_key_f: Map<Object, Map<string, Set<Function>>> = new Map();

    public getObserverByTargetKey(target: Object, key: string | symbol): Set<Function> | null {
        if (typeof key == "symbol")
            return new Set();
        let map = this._t_key_f.get(target);
        if (map) {
            let obs = map.get(key);
            if (obs)
                return obs;
        }
        return null;
    }

    // 添加一个监听对象
    public addObserverTarget(obf: Function, target: Object, key: string | symbol) {
        // 在以要执行的观察者函数为键值的map中注册
        if (typeof key == "symbol")
            return;
        let map = this._f_t_key.get(obf);
        if (!map) {
            map = new Map();
            this._f_t_key.set(obf, map);
        }
        let set = map.get(target);
        if (!set) {
            set = new Set();
            map.set(target, set);
        }
        set.add(key);

        // 在以targert为键值的map中注册
        let map2 = this._t_key_f.get(target);
        if (!map2) {
            map2 = new Map();
            this._t_key_f.set(target, map2);
        }
        let set2 = map2.get(key);
        if (!set2) {
            set2 = new Set();
            map2.set(key, set2);
        }
        set2.add(obf);
    }

    public removeObserverTargets(obf: Function) {
        let targets = this._f_t_key.get(obf);
        this._f_t_key.delete(obf);

        if (!targets)
            return;
        targets.forEach((set: Set<string>, t: Object) => {
            let map = this._t_key_f.get(t);
            set.forEach((key) => {
                map?.get(key)?.delete(obf);
            });
        });
    }
}

const obMgr = new ObserverMgr();
// 代理map管理所有被监察对象的get set
const proxies = new Map();

let observerStack: Array<Function> = [];


let _nextTickFunc: Set<Function> = new Set();
let _freeFuncs: Array<Function> = new Array();

export function isObservable(obj: Object) {
    return proxies.get(obj);
}

let _dirtys = new Set();
let _modes = new Map();
let _getMode = function (fn: Function) {
    return _modes.get(fn);
};


export function toObservable(obj: Object) {
    let proxy = new Proxy(obj, {
        get: function (target, key, receiver) {
            if (observerStack.length > 0) {
                obMgr.addObserverTarget(observerStack[observerStack.length - 1], target, key);
            }
            let map = _bindings.get(proxies.get(obj));
            let fn = map?.get(key);
            if (fn && _dirtys.has(fn)) {
                _runAndCollectTargets(fn);
            }
            return Reflect.get(target, key, receiver);
        },
        set: function (target, key, value, receiver) {
            Reflect.set(target, key, value, receiver);
            let set = obMgr.getObserverByTargetKey(target, key);
            if (set) {
                let fns: Array<Function> = [];
                set.forEach((fn: Function) => {
                    fns.push(fn);
                });
                for (let i = 0; i < fns.length; i++) {
                    let fn = fns[i];
                    if (_getMode(fn) != Update_mode.Immediately) {
                        _dirtys.add(fn);
                    } else {
                        _runAndCollectTargets(fns[i]);
                    }
                }
            }

            return true;
        }
    });
    proxies.set(obj, proxy);
    return proxy;
}

export function observable(obj: Object) {
    return isObservable(obj) ? proxies.get(obj) : toObservable(obj);
}

let _runAndCollectTargets = function (func: Function) {
    _dirtys.delete(func);
    obMgr.removeObserverTargets(func);
    observerStack.push(func);
    let ret = func();
    observerStack.pop();
    return ret;
};

export function autorun(func: Function, mode: Update_mode = Update_mode.Immediately) {
    _modes.set(func, mode);
    switch (mode) {
        case Update_mode.Immediately:
            _runAndCollectTargets(func);
            break;
        case Update_mode.NextTick:
            _dirtys.add(func);
            _nextTickFunc.add(func);
            break;
        case Update_mode.OnFree:
            _dirtys.add(func);
            _freeFuncs.push(func);
            break;
    }
}

export function updateObservers() {
    _nextTickFunc.forEach((func: Function) => {
        if (_dirtys.has(func)) {
            _runAndCollectTargets(func);
        }
    });
    for (let i = 0; i < _freeFuncs.length; i++) {
        let f = _freeFuncs[i];
        if (_dirtys.has(f)) {
            _runAndCollectTargets(f);
            break;
        }
    }
}

let _bindings: Map<object, Map<string | symbol, Function>> = new Map();
export function bind(proxy: any, key: string, fn: Function, mode: Update_mode = Update_mode.Immediately) {
    let func = () => {
        proxy[key] = fn();
    };

    autorun(func, mode);
    let map = _bindings.get(proxy);
    if (!map) {
        map = new Map();
        _bindings.set(proxy, map);
    }
    map.set(key, func);
}